# -*-coding:Utf-8 -*
""" Controlled object Package
"""
import bge,mathutils,math,random,functools
from time import time
from ..common import *
from ..inputs import *
from .item import UsableItem

IN_UP = 0
IN_LEFT = 1
IN_DOWN = 2
IN_RIGHT = 3

class MouseView(BasicEvent):
	
	class Dimension(object):
		def __init__(self):
			self._sens = 1.0
			self._limit = None
			self.rotation = 0.0
		
		@property
		def limit(self):
			if type(self._limit) == list:
				return tuple(self._limit)
			return self._limit
		@limit.setter
		def limit(self,value):
			if type(value) == list and len(value) > 1 and value[0]>=value[1]:
				self._limit = [value[0],value[1]]
			elif type(value) == float:
				self._limit = [abs(value),-abs(value)]
			elif type(value) == int:
				self._limit = [math.radians(abs(value)),-math.radians(abs(value))]
			elif not value:
				self._limit = None
		@property
		def limitpos(self):
			if type(self._limit) == list:
				return self._limit[0]
			return self._limit
		@limitpos.setter
		def limitpos(self,value):
			if type(value) == float:
				self._limit[0] = value
			elif type(value) == int:
				self._limit[0] = math.radians(value)
		@property
		def limitneg(self):
			if type(self._limit) == list:
				return self._limit[1]
			return self._limit
		@limitneg.setter
		def limitneg(self,value):
			if type(value) == float:
				self._limit[1] = -value
			elif type(value) == int:
				self._limit[1] = -math.radians(value)
		
		@property
		def sensitivity(self):
			return abs(self._sens)
		@sensitivity.setter
		def sensitivity(self,value):
			try:
				value = float(value)
			except:
				return
			if self.invert:
				self._sens = -value
			else:
				self._sens = value
		
		@property
		def invert(self):
			if self._sens < 0:
				return True
			return False
		@invert.setter
		def invert(self,value):
			if value:
				self._sens = -abs(self._sens)
			else:
				self._sens = abs(self._sens)
				
		@property
		def dRot(self):
			return math.degrees(self.rotation)
		@property
		def rRot(self):
			return self.rotation
			
		def applyRot(self, value):
			rot = value*self._sens
			if not self._limit:
				self.rotation += rot
				self.rotation = ((self.rotation+math.pi)%(2*math.pi))-math.pi
			else:
				if self.rotation+rot > self._limit[0]:
					rot = self._limit[0]-self.rotation
					self.rotation = self._limit[0]
				elif self.rotation+rot < self._limit[1]:
					rot = self._limit[1]-self.rotation
					self.rotation = self._limit[1]
				else:
					self.rotation += rot
			return rot
	
	def __init__(self):
		BasicEvent.__init__(self)
		self.x = MouseView.Dimension()
		self.y = MouseView.Dimension()
		self.mouse = MouseTracking()
		
	def update(self):
		if self.mouse.axex:
			self.x.applyRot(self.mouse.axex)
		if self.mouse.axey:
			self.y.applyRot(self.mouse.axey)
			

class ControlledMovement(Twin):
	
	def __init__(self, 
					inpMap, 
					up = bge.events.UPARROWKEY,
					left = bge.events.LEFTARROWKEY,
					down = bge.events.DOWNARROWKEY,
					right = bge.events.RIGHTARROWKEY):
		motherOb = None
		if not issubclass(type(inpMap),InputMap):
			motherOb = inpMap
			inpMap = motherOb.settings['inputsMap']
		Twin.__init__(self, motherOb,{'speed':[5,5,0],
				'multiply':[1,1,1],
				'inputsMap':inpMap,
				'inputs':(Input(inpMap),
					Input(inpMap),
					Input(inpMap),
					Input(inpMap)) },
				{'move_keys':mathutils.Vector((0.0, 0.0)),
				'move':mathutils.Vector((0.0, 0.0)),
				'velocity':mathutils.Vector((.0,.0))})
		
		keys = [up,left,down,right]
		acts = [self.forward,self.left,self.back,self.right]
		for i in range(0,4):
			inpkeys = keys[i]
			if type(inpkeys) in (list,tuple):
				self.settings['inputs'][i].addKey(*inpkeys).addAct(acts[i])
			else:
				self.settings['inputs'][i].addKey(inpkeys).addAct(acts[i])
	
	def refresh_inputs(self):
		if not self.refresh_move():
			self.__refresh_link__(self)
		return
	
	def __link_stable__(self):
		return self.refresh_move()
		
	@property
	def move(self):
		return self.stats['move_keys'].to_tuple()
	@property
	def real_move(self):
		return self.stats['velocity'].to_tuple()
		
	def forward(self,active):
		move_vector = self.stats['move_keys']
		move_inputs = self.settings['inputs']
		if active:
			move_vector.x = 1.0
		elif move_inputs[IN_DOWN]:
			move_vector.x = -1.0
		else:
			move_vector.x = 0.0
		self.refresh_inputs()
	def back(self,active):
		move_vector = self.stats['move_keys']
		move_inputs = self.settings['inputs']
		if active:
			move_vector.x = -1.0
		elif move_inputs[IN_UP]:
			move_vector.x = 1.0
		else:
			move_vector.x = 0.0
		self.refresh_inputs()
	def left(self,active):		
		move_vector = self.stats['move_keys']
		move_inputs = self.settings['inputs']
		if active:
			move_vector.y = -1.0
		elif move_inputs[IN_RIGHT]:
			move_vector.y = 1.0
		else:
			move_vector.y = 0.0
		self.refresh_inputs()
	def right(self,active):
		move_vector = self.stats['move_keys']
		move_inputs = self.settings['inputs']
		if active:
			move_vector.y = 1.0
		elif move_inputs[IN_LEFT]:
			move_vector.y = -1.0
		else:
			move_vector.y = 0.0
		self.refresh_inputs()
	
	def refresh_move(self):
		self.stats['move'] = self.stats['move_keys']
		mov_vec = self.stats['move_keys'].normalized()
		if (mov_vec.x,mov_vec.y) == (0.0,0.0):
			self.stats['velocity'] = mathutils.Vector((0.0,0.0))
			return False
		else:
			self.stats['velocity'] = mathutils.Vector((
				mov_vec.x*self.settings['speed'][0]*self.settings['multiply'][0],
				mov_vec.y*self.settings['speed'][1]*self.settings['multiply'][1]))
			return True

class JoystickMovement(Twin):
	
	def __init__(self, 
					inpMap, 
					updown = JoystickEvent(0,AXIS(1)),
					leftright = JoystickEvent(0,AXIS(0))):
		motherOb = None
		if not issubclass(type(inpMap),InputMap):
			motherOb = inpMap
			inpMap = motherOb.settings['inputsMap']
		Twin.__init__(self, motherOb,{'speed':[5,5,0],
				'multiply':[1,1,1],
				'inputsMap':inpMap,
				'joy_inputs':(AxisInput(inpMap,updown).addAct(self.x_velocity),
							AxisInput(inpMap,leftright).addAct(self.y_velocity)) },
				{'joy_move':mathutils.Vector((0.0, 0.0)),
				'move':mathutils.Vector((0.0, 0.0)),
				'velocity':mathutils.Vector((.0,.0))})
		self.refresh = True
				
	def refresh_inputs(self):
		if self.refresh:
			self.settings['inputsMap'].add_callbacks(self.__refresh_link__)
			self.refresh = False
	
	def __link_stable__(self):
		return self.refresh_move()
				
	def x_velocity(self, value):
		self.stats['joy_move'].x = value
		self.refresh_inputs()
		
	def y_velocity(self, value):
		self.stats['joy_move'].y = value
		self.refresh_inputs()
	
	def refresh_move(self):
		self.stats['move'] = self.stats['joy_move']
		mov_vec = self.stats['joy_move'].normalized()
		self.refresh = True
		if (mov_vec.x,mov_vec.y) == (0.0,0.0):
			self.stats['velocity'] = mathutils.Vector((0.0,0.0))
			return False
		else:
			self.stats['velocity'] = mathutils.Vector((
				mov_vec.x*self.settings['speed'][0]*self.settings['multiply'][0],
				mov_vec.y*self.settings['speed'][1]*self.settings['multiply'][1]))
			return True

class HeldItem(Twin):
	
	useMethod = ()
	
	class defaultNulItem(object):
		def first(s,self,active):
			self.first = active
	
		def second(s,self,active):
			self.second = active
		
		def third(s,self,active):
			self.third = active
			
		def taken(s,self):
			return
	
	def __init__(self,
				inpMap, 
				basic_item = None,
				firstUse = bge.events.LEFTMOUSE,
				secondUse = bge.events.RIGHTMOUSE, 
				thirdUse = bge.events.MIDDLEMOUSE):
		motherOb = None
		basic_item = HeldItem.checkItem(basic_item,defaultNulItem(),True)
		if not issubclass(type(inpMap),InputMap):
			motherOb = inpMap
			inpMap = motherOb.settings['inputsMap']
		Twin.__init__(self, motherOb,{'inputsMap':inpMap,
						'default_item':basic_item,
						'item_input':[Input(inpMap,firstUse).addAct(self.first_use),
						Input(inpMap,secondUse).addAct(self.second_use),
						Input(inpMap,thirdUse).addAct(self.third_use)]
						},
					{'primary_object':basic_item})
		self.checkFunc()
		self.activity = [False,False,False]
		self.stats['primary_object'].taken(self)
	@classmethod
	def checkItem(cls, item, error_return = False, it_return = False):
		if not issubclass(type(item),UsableItem) or \
		 not all(hasattr(item,a) for a in ('first','second','third','taken')):
			return error_return
		if it_return:
			return item
		return True
		
	def GetActionFrom(self,name):
		if type(name) != str or name not in self.__class__.useMethod:
			return self.doNothing
		if not hasattr(self,name+"Func"):
			setattr(self,name+"Func",self.doNothing)
		return functools.partial(self._action,name+"Func")
		
	def _action(self,name,*args,**kwargs):
		getattr(self,name)(*args,**kwargs)
	
	def checkFunc(self):
		for aMethod in self.__class__.useMethod:
			if not hasattr(self.stats['primary_object'],aMethod):
				setattr(self,aMethod+'Func',self.doNothing)
			else:
				setattr(self,aMethod+'Func',getattr(self,aMethod))
		
	def doNothing(self,*args,**kwargs):
		return
		
	def first_use(self,active):
		self.activity[0] = bool(active)
		self.stats['primary_object'].first(self,active)
	
	def second_use(self,active):
		self.activity[1] = bool(active)
		self.stats['primary_object'].second(self,active)
	
	def third_use(self,active):
		self.activity[2] = bool(active)
		self.stats['primary_object'].third(self,active)
		
	def change_item(self,item):
		if HeldItem.checkItem(item):
			if self.activity[0]:
				self.first_use(0.0)
			if self.activity[1]:
				self.second_use(0.0)
			if self.activity[2]:
				self.third_use(0.0)
			self.stats['primary_object'] = item
			self.checkFunc()
			self.stats['primary_object'].taken(self)
			
		
		
